using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.XPath;
using Syno.Core.Output;
using Syno.Core.Output.DataDisplay;
using Syno.Core.Output.DataLoader;
using Syno.Core.Output.OutputFormat;
using Syno.Core.Xml.Data;

namespace Syno.Core.Xml
{
    public class XmlReader
    {
        public event XsdErrorHandler OnXsdError;

        public void InvokeOnXsdError(ValidationEventArgs args)
        {
            XsdErrorHandler handler = OnXsdError;
            if (handler != null) handler(this, args);
        }

        public SynoConfig ReadFile(string fileName)
        {
            
            var shemas = new XmlSchemaSet();
            Assembly assem = typeof(XmlReader).Assembly;
            using (Stream stream = assem.GetManifestResourceStream("Syno.Core.Xml.SynoSchema.xsd"))
            {
                if (stream != null)
                {
                    shemas.Add("", System.Xml.XmlReader.Create(stream));
                }
                else
                {
                    throw new Exception("Could not load embedded resource SynoSchema.xsd");
                }
            }
            XDocument element = XDocument.Load(fileName);
            element.Validate(shemas, (sender, e) => InvokeOnXsdError(e), true);
            var synoConfig = new SynoConfig();
            var profiles = element.XPathSelectElements("//profiles/profile");
            LoadProfile(synoConfig, profiles);
            var dataSource = element.XPathSelectElements("//dataSources/dataSource");
            LoadDataSource(synoConfig, dataSource);
            var dataHolders = element.XPathSelectElements("//dataHolders/dataHolder");
            LoadDataHolders(synoConfig, dataHolders);
            LoadParameter(synoConfig.Parameters, element.XPathSelectElements("/synoConfig/parameters/parameter"));
            return synoConfig;
        }

        private void LoadParameter(IDictionary<string, string> parameters, IEnumerable<XElement> element)
        {
            parameters.Clear();
            foreach (var xElement in element)
            {
                parameters.Add(xElement.Attribute("name").AsString(),xElement.AsString());
            }
        }

        private void LoadDataHolders(SynoConfig synoConfig, IEnumerable<XElement> dataHolders)
        {
            foreach (var dataHolder in dataHolders)
            {

                var dh = new DataHolder(dataHolder.Attribute("name").AsString(), GetDataLoader(dataHolder.Element("dataLoader")), GetDataDisplay(dataHolder.Element("dataDisplay")));

                synoConfig.DataHolders.Add(dh.Id, dh);
            }
        }

        private IDataDisplay GetDataDisplay(XElement elements)
        {
            foreach (var element in elements.Elements())
            {
                if (element != null && element.Name == "htmlTableDisplay")
                {
                    return new HtmlTableDisplay() {Heading = element.Attribute("heading").AsString()};
                }
                if (element != null && element.Name == "googleSingleLineGraphDisplay")
                {
                    return new GoogleSingleLineGraphDisplay(
                        element.Attribute("width").AsInt(),
                        element.Attribute("height").AsInt(),
                        element.Attribute("valueColumn").AsString(),
                        element.Attribute("yLables").AsString(),
                        element.Attribute("legendColumn").AsString()
                        ) { Heading = element.Attribute("heading").AsString(),
                            MaxBottomLables = element.Attribute("maxBottomLables").AsInt(5)
                        
                        
                    };
                }
                if (element != null && element.Name == "textTableDisplay")
                {
                    return new TextTableDisplay() {Heading = element.Attribute("heading").AsString()};
                }

                if (element != null && element.Name == "templateDisplay")
                {
                    return new TemplateDisplay() {Template = element.AsString()};
                }

                if (element != null && element.Name == "singleValue")
                {
                    return new SingleValueResult() ;
                }
                

                
            }
            throw new Exception("Data display not recognized " + elements);
        }

        private IDataLoader GetDataLoader(XElement dataHolders)
        {
            foreach (var dataHolder in dataHolders.Elements())
            {
                if (dataHolder != null && dataHolder.Name == "sqlDataLoader")
                {
                    return new SqlDataLoader(null) { Sql = dataHolder.AsString() };
                }
            }
            throw new Exception("Data loader not recognized " + dataHolders);
        }

        private void LoadDataSource(SynoConfig synoConfig, IEnumerable<XElement> dataSource)
        {
            foreach (var element in dataSource)
            {
                if (element.Attribute("type").AsString() == "sqlServer")
                {
                    synoConfig.DataSource.Add(element.Attribute("name").AsString(),
                                              new SqlServer(
                                                  element.Attribute("connectionString").AsString(),
                                                  element.Attribute("timeout").AsInt()));
                }
                else
                {
                    throw new Exception("Unknown data source type");
                }
            }

        }

        private void LoadProfile(SynoConfig synoConfig, IEnumerable<XElement> elements)
        {
            foreach (var xElement in elements)
            {

                var profile = new Profile()
                {
                    Name = xElement.Attribute("name").AsString(""),
                    DefaultDataSource = xElement.Attribute("defaultDataSource").AsString("")
                };
                LoadProfileDataHolders(profile, xElement.Element("loadDataHolders"));
                LoadProfileOutput(profile, xElement.Element("output"));
                LoadParameter(profile.Parameters, xElement.XPathSelectElements("parameters/parameter"));
                synoConfig.Profile.Add(profile.Name, profile);
            }
        }

        private void LoadProfileDataHolders(Profile profile, XElement element)
        {
            foreach (var xElement in element.Elements("load"))
            {
                try
                {
                    profile.DataLoaders.Add(xElement.Attribute("name").AsString(), xElement.Attribute("dataSource").AsString(profile.DefaultDataSource));
                }
                catch (Exception e)
                {
                    throw new Exception(string.Format("Cant add a data loader with the same name [{0}]", xElement.Attribute("name").AsString()));
                }
            }
        }

        private void LoadProfileOutput(Profile profile, XElement element)
        {
            foreach (var xElement in element.Elements())
            {
                if (xElement.Name == "htmlFile")
                {
                    profile.Output = new HtmlFileOutput(xElement.Attribute("fileName").AsString(""))
                    {
                        Template = xElement.Element("template").AsString("Please add template")
                    };
                }
                else if (xElement.Name == "email")
                {
                    profile.Output = new EmailFormatOutput(xElement.Attribute("from").AsString(""), xElement.Attribute("to").AsString(""), xElement.Attribute("isHtml").AsBoolean())
                    {
                        Body = xElement.Element("body").AsString("Body"),
                        Subject = xElement.Element("subject").AsString("Subject")
                    };
                }
                else
                {
                    throw new Exception("Unknowen element in profile output");
                }
            }
        }


        public delegate void XsdErrorHandler(object sender, ValidationEventArgs args);    
    }

    
}